package edu.gatech.cs2340.teamrocket.control.turnlogic;

import java.util.Collections;
import java.util.List;
import java.util.ArrayList;
import javax.swing.JOptionPane;
import edu.gatech.cs2340.teamrocket.model.Model;
import edu.gatech.cs2340.teamrocket.model.item.Item;
import edu.gatech.cs2340.teamrocket.model.pokemon.Pokemon;
import edu.gatech.cs2340.teamrocket.model.team.Team;
import edu.gatech.cs2340.teamrocket.model.map.City;
import edu.gatech.cs2340.teamrocket.model.map.Lakeside;
import edu.gatech.cs2340.teamrocket.model.map.Location;
import edu.gatech.cs2340.teamrocket.model.map.Town;
import edu.gatech.cs2340.teamrocket.model.pokemon.ailments.Ailment;
import edu.gatech.cs2340.teamrocket.model.pokemon.ailments.AilmentFactory;
import edu.gatech.cs2340.teamrocket.model.pokemon.ailments.NoAilment;
import edu.gatech.cs2340.teamrocket.model.pokemon.ailments.Pokerus;

/**
 * This class does everything that needs to be done when the player presses
 * "Continue." It will update the Model objects and return a list of random
 * events that occurred.
 *
 * @author Joe Tacheron
 */
public class DefaultTurnLogic implements TurnLogic {

    private static final double TAUROS_FAINTED_EVENT_PROB   = 0.01;
    private static final double WAGON_FLIP_EVENT_PROB       = 0.3;
    private static final double WEATHER_CHANGE_EVENT_PROB   = 0.3;
    private static final double AILMENT_EVENT_PROB          = 0.02;
    private static final double DAMAGE_EVENT_PROB           = 0.03;

    private static final int MAX_DAMAGE = 20;
    private static final int MIN_DAMAGE = 5;

    private static final int DAY_STEP = 4;

    private static final String CHOICE_FERRY = "S.S. Anne";
    private static final String CHOICE_SURF = "Surf";
    private static final String CHOICE_SWIM = "Swim";
    private static final String[] RIVER_CHOICES = {CHOICE_FERRY, CHOICE_SURF, CHOICE_SWIM};
    private static final int FERRY_COST = 500;
    private static final int SURF_COST = 10;

    private Model model;

    public DefaultTurnLogic(){
        model = Model.getInstance();
    }

    @Override
    public List<String> nextTurn() {
        List<String> messages = new ArrayList<String>();
        messages.addAll(updateTravelers());
        messages.addAll(updateInventory());
        messages.addAll(updateEnvironment());
        messages.addAll(updateStats());
        // map has to be updated last since the wagon speed may change b/c of
        // travelers, inventory, or environment
        messages.addAll(updateMap());
        messages.removeAll(Collections.singleton(null));
        // update server does nothing now
        updateServer();
        // add all messages to the model
        model.addUpdates(messages);
        return messages;
    }

    @Override
    public List<String> updateTravelers() {
        // call update on all Travelers
        List<String> updates = new ArrayList<String>();

        for(Pokemon t : model.getPokemon()){
            if(!t.isFainted()){
                int foodAmount = Math.min(model.getTeamRations(), model.getBackpackQuantity(Item.Ether));

                // update pp
                t.addPP(foodAmount);

                // update health
                if(Math.random() < DAMAGE_EVENT_PROB){
                    int dmg = MIN_DAMAGE + ((int)(Math.random() * (MAX_DAMAGE - MIN_DAMAGE)));
                    t.takeDamage(dmg);
                    if(t.isFainted()){
                        updates.add(t + " has fainted from battle.");
                        return updates;
                    }
                    else{
                        updates.add(t + " has taken " + dmg + " damage from battle.");
                    }
                }

                // update ailments
                Ailment ailment = t.getAilment();
                if(ailment instanceof NoAilment && Math.random() < AILMENT_EVENT_PROB){
                    t.setAilment(AilmentFactory.random());
                    updates.add(ailment.catchMessage(t));
                }
                updates.add(ailment.affect(t));
            }
        }

        return updates;
    }

    @Override
    public List<String> updateInventory() {
        // remove food equal to rations * (number of living travelers)
        boolean hasFoodToStart = model.getBackpackQuantity(Item.Ether) != 0;
        List<String> updates = new ArrayList<String>();

        int foodEaten = Math.min(   model.getBackpackQuantity(Item.Ether),
                model.getTeamRations() * model.getPokemon().size());
        model.removeItem(Item.Ether, foodEaten);
        if(hasFoodToStart && model.getBackpackQuantity(Item.Ether) == 0)
            updates.add("The group has run out of food.");

        // check there are enough Tauros to support desired speed
        int numTauros = model.getBackpackQuantity(Item.Tauros);
        if(model.getTeamSpeed() >= Team.MID_SPEED + numTauros){
            model.setTeamSpeed(Team.MID_SPEED + numTauros - 1);
            updates.add("You cannot move at that speed without enough Tauros");
        }

        // Tarous may faint event
        if(Math.random() < TAUROS_FAINTED_EVENT_PROB && model.backpackContains(Item.Tauros)){
            model.removeItem(Item.Tauros);
            updates.add("One of your Tauros fainted from exhaustion!");
        }

        return updates;
    }

    @Override
    public List<String> updateMap() {
        List<String> updates = new ArrayList<String>();

        // check to see everyone is able to move
        boolean canMove = true;
        for(Pokemon t : model.getPokemon()){
            if(!t.canMove()){
                canMove = false;
                updates.add("You could not move forward!");
                break;
            }
        }

        Location newLocation = null;
        if(canMove){
            newLocation = model.moveForward(model.getTeamSpeed());
            if(newLocation != null) {
                updates.add("You are now entering " + newLocation);

                // river crossing
                if(newLocation instanceof Lakeside){
                    int choice = JOptionPane.showOptionDialog(
                        null,
                        "How do you wish to cross the water?",
                        "You have reached a body of water!",
                        JOptionPane.DEFAULT_OPTION,
                        JOptionPane.QUESTION_MESSAGE,
                        null,
                        RIVER_CHOICES,
                        RIVER_CHOICES[0]
                    );
                    // swim choice
                    if(RIVER_CHOICES[choice].equals(CHOICE_SWIM)){
                        if(Math.random() < WAGON_FLIP_EVENT_PROB){
                            model.clearInventory();
                            updates.add("You fell and lost all your supplies in the water!");
                        }
                    }
                    // surf choice
                    else if(RIVER_CHOICES[choice].equals(CHOICE_SURF)){
                        for(Pokemon t : model.getPokemon()){
                            if(!t.isFainted()){
                                t.removePP(SURF_COST);
                                updates.add(t + " used surf and lost " + SURF_COST + " PP!");
                                if(t.isFainted()){
                                    updates.add(t + " fainted from having no PP!");
                                }
                                break;
                            }
                        }
                    }
                    // ferry choice
                    else if(RIVER_CHOICES[choice].equals(CHOICE_FERRY)){
                        int price = (int)Math.min(model.getBackpack().getMoney(), FERRY_COST);
                        updates.add("You spent $" + price + " on a ticket to cross the river");
                        model.getBackpack().deductMoney(price);
                    }
                    else{
                        throw new RuntimeException("Undefined input for river crossing: " + choice);
                    }
                }
                // poke center
                else if (newLocation instanceof City || newLocation instanceof Town){
                    for(Pokemon t : model.getPokemon()){
                        t.revive();
                    }
                    updates.add("You visited the Poke Center and feel revitalized!");
                }
            }
        }

        // update pokemon based on final movement speed
        for(Pokemon t : model.getPokemon()){
            if(!t.isFainted() && !(newLocation instanceof City || newLocation instanceof Town)){

                // remove PP based on Team speed
                if(canMove){
                    t.removePP(model.getTeamSpeed());
                }
                else{
                    t.removePP(Team.MIN_SPEED);
                }
                if(t.isFainted()){
                    updates.add(t + " has fainted from having no PP.");
                }
            }
        }

        return updates;
    }

    @Override
    public List<String> updateEnvironment() {
        List<String> updates = new ArrayList<String>();

        // increment day
        model.incDay(DAY_STEP);
        // change weather
        if(Math.random() < WEATHER_CHANGE_EVENT_PROB)
            model.changeWeather();
        // set max wagon speed
        if(model.getTeamSpeed() > model.getMaxWagonSpeed()){
            model.setTeamSpeed(model.getMaxWagonSpeed());
            updates.add("You are impeded by the " + model.getWeather() + " conditions.");
        }

        return updates;
    }

    @Override
    public List<String> updateStats() {
        List<String> updates = new ArrayList<String>();

        // increment turn count
        model.incTurn();
        // increment score
        int scoreInc = 0;
        for(Pokemon t : model.getPokemon()){
            scoreInc += (t.getAilment() instanceof Pokerus ? Pokerus.SCORE_MULT : 1) * t.getHealth();
            scoreInc -= (Pokemon.MAX_PP - t.getPP());
        }
        int mult = model.getTrainerJob().getScoreMultiplier();
        model.incScore(mult * scoreInc);

        return updates;
    }

    @Override
    public void updateServer() {
    }
}